home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 November: Tool Chest / Dev.CD Nov 00 TC Disk 1.toast / What's New? / Sample Code / Sound / MP3Player / PlaySound.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-09-28  |  17.6 KB  |  457 lines  |  [TEXT/CWIE]

  1. /*
  2.     File:        PlaySound.c
  3.     
  4.     Description: Play Sound shows how to use the Sound Description Extention atom information with the
  5.                  SoundConverter APIs to play non VBR MP3 files as well as other types of sound files
  6.                  using various encoding methods.
  7.  
  8.     Author:        mc, era
  9.  
  10.     Copyright:     © Copyright 2000 Apple Computer, Inc. All rights reserved.
  11.     
  12.     Disclaimer:    IMPORTANT:  This Apple software is supplied to you by Apple Computer, Inc.
  13.                 ("Apple") in consideration of your agreement to the following terms, and your
  14.                 use, installation, modification or redistribution of this Apple software
  15.                 constitutes acceptance of these terms.  If you do not agree with these terms,
  16.                 please do not use, install, modify or redistribute this Apple software.
  17.  
  18.                 In consideration of your agreement to abide by the following terms, and subject
  19.                 to these terms, Apple grants you a personal, non-exclusive license, under Apple’s
  20.                 copyrights in this original Apple software (the "Apple Software"), to use,
  21.                 reproduce, modify and redistribute the Apple Software, with or without
  22.                 modifications, in source and/or binary forms; provided that if you redistribute
  23.                 the Apple Software in its entirety and without modifications, you must retain
  24.                 this notice and the following text and disclaimers in all such redistributions of
  25.                 the Apple Software.  Neither the name, trademarks, service marks or logos of
  26.                 Apple Computer, Inc. may be used to endorse or promote products derived from the
  27.                 Apple Software without specific prior written permission from Apple.  Except as
  28.                 expressly stated in this notice, no other rights or licenses, express or implied,
  29.                 are granted by Apple herein, including but not limited to any patent rights that
  30.                 may be infringed by your derivative works or by other works in which the Apple
  31.                 Software may be incorporated.
  32.  
  33.                 The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO
  34.                 WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
  35.                 WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  36.                 PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
  37.                 COMBINATION WITH YOUR PRODUCTS.
  38.  
  39.                 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR
  40.                 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  41.                 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  42.                 ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
  43.                 OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
  44.                 (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
  45.                 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  46.                 
  47.     Change History (most recent first): <2> 7/26/00 sans hickup and carbonized
  48.                                         <1> 4/01/00 initial release
  49. */
  50.  
  51. #include "MP3Player.h"
  52.  
  53. // globals
  54. Boolean gBufferDone = false;
  55.  
  56. // * ----------------------------
  57. // MySoundCallBackFunction
  58. //
  59. // used to signal when a buffer is done playing
  60. static pascal void MySoundCallBackFunction(SndChannelPtr theChannel, SndCommand *theCmd);
  61. static pascal void MySoundCallBackFunction(SndChannelPtr theChannel, SndCommand *theCmd)
  62. {
  63. #pragma unused(theChannel)
  64.     
  65.     #ifndef TARGET_API_MAC_CARBON
  66.         #if !GENERATINGCFM
  67.             long oldA5;
  68.             oldA5 = SetA5(theCmd->param2);
  69.         #else
  70.             #pragma unused(theCmd)
  71.         #endif
  72.     #else
  73.         #pragma unused(theCmd)
  74.     #endif // TARGET_API_MAC_CARBON
  75.  
  76.     gBufferDone = true;
  77.  
  78.     #ifndef TARGET_API_MAC_CARBON
  79.         #if !GENERATINGCFM
  80.             oldA5 = SetA5(oldA5);
  81.         #endif
  82.     #endif // TARGET_API_MAC_CARBON
  83. }
  84.  
  85. // * ----------------------------
  86. // MyGetSoundDescriptionExtension
  87. //
  88. // this function will extract the information needed to decompress the sound file, this includes retrieving the sample description,
  89. // the decompression atom, setting up the sound header, copying the sample data into a sample buffer and calculating it's length
  90. static OSErr MyGetSoundDescriptionExtension(const FSSpec *inMP3file, AudioFormatAtomPtr *outAudioAtom, CmpSoundHeaderPtr outSoundHeader, Handle outSampleBuf, UInt32 *outLength);
  91. static OSErr MyGetSoundDescriptionExtension(const FSSpec *inMP3file, AudioFormatAtomPtr *outAudioAtom, CmpSoundHeaderPtr outSoundHeader, Handle outSampleBuf, UInt32 *outLength)
  92. {
  93.     Movie theMovie;
  94.     Track theTrack;
  95.     Media theMedia;
  96.     short theRefNum;
  97.     short theResID = 0;    // we want the first movie
  98.     Boolean wasChanged;
  99.     
  100.     OSErr err = noErr;
  101.     
  102.     // open the movie file
  103.     err = OpenMovieFile(inMP3file, &theRefNum, fsRdPerm);
  104.     BailErr(err);
  105.  
  106.     // instantiate the movie
  107.     err = NewMovieFromFile(&theMovie, theRefNum, &theResID, NULL, newMovieActive, &wasChanged);
  108.     BailErr(err);
  109.     CloseMovieFile(theRefNum);
  110.     theRefNum = 0;
  111.         
  112.     // get the first sound track
  113.     theTrack = GetMovieIndTrackType(theMovie, 1, SoundMediaType, movieTrackMediaType);
  114.     if (theTrack != NULL) {
  115.     
  116.         // get the sound track media
  117.         theMedia = GetTrackMedia(theTrack);
  118.         if (theMedia != NULL) {            
  119.             Size size;
  120.             Handle extension;
  121.             
  122.             // Version 1 of this record includes four extra fields to store information about compression ratios. It also defines
  123.             // how other extensions are added to the SoundDescription record.
  124.             // All other additions to the SoundDescription record are made using QT atoms. That means one or more
  125.             // atoms can be appended to the end of the SoundDescription record using the standard [size, type]
  126.             // mechanism used throughout the QuickTime movie resource architecture.
  127.             // http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/frameset.htm
  128.             SoundDescriptionV1Handle sourceSoundDescription = (SoundDescriptionV1Handle)NewHandle(0);
  129.             
  130.             // get the description of the sample data
  131.             GetMediaSampleDescription(theMedia, 1, (SampleDescriptionHandle)sourceSoundDescription);
  132.             err = GetMoviesError();
  133.  
  134.             extension = NewHandle(0);
  135.             
  136.             // get the "magic" decompression atom
  137.             // This extension to the SoundDescription information stores data specific to a given audio decompressor.
  138.             // Some audio decompression algorithms require a set of out-of-stream values to configure the decompressor
  139.             // which are stored in a siDecompressionParams atom. The contents of the siDecompressionParams atom are dependent
  140.             // on the audio decompressor.
  141.             err = GetSoundDescriptionExtension((SoundDescriptionHandle)sourceSoundDescription, &extension, siDecompressionParams);
  142.             
  143.             if (noErr == err) {
  144.                 size = GetHandleSize(extension);
  145.                 HLock(extension);
  146.                 *outAudioAtom = (AudioFormatAtom*)NewPtr(size);
  147.                 err = MemError();
  148.                 // copy the atom data to our buffer...
  149.                 BlockMoveData(*extension, *outAudioAtom, size);
  150.                 HUnlock(extension);
  151.             } else {
  152.                 // if it doesn't have an atom, that's ok
  153.                 err = noErr;
  154.             }
  155.             
  156.             // set up our sound header
  157.             outSoundHeader->format = (*sourceSoundDescription)->desc.dataFormat;
  158.             outSoundHeader->numChannels = (*sourceSoundDescription)->desc.numChannels;
  159.             outSoundHeader->sampleSize = (*sourceSoundDescription)->desc.sampleSize;
  160.             outSoundHeader->sampleRate = (*sourceSoundDescription)->desc.sampleRate;
  161.             
  162.             if (noErr == err) {
  163.                 // copy the sample data into our buffer
  164.                 TimeValue trackDuration = GetTrackDuration(theTrack);    
  165.                 TimeValue startTime = 0;
  166.                 long numMediaSamples = GetMediaSampleCount(theMedia);
  167.                 long numberOfSamples;
  168.                 Handle hTemp = NewHandle(0);
  169.                 
  170.                 do {
  171.                     err = GetMediaSample(theMedia, hTemp, 0L, NULL, startTime, NULL, 0, NULL, NULL, numMediaSamples, &numberOfSamples, NULL);
  172.                     startTime += numberOfSamples;
  173.                     HandAndHand(hTemp, outSampleBuf);
  174.                 } while (startTime < numMediaSamples && noErr == err );
  175.                 DisposeHandle(hTemp);
  176.                 
  177.                 // calculate the duration of the data
  178.                 
  179.                 // although we should be able to use the samplesPerPacket value returned in the SoundDescriptionV1 record to see if we're dealing with multiple samples per packet
  180.                 // as per the documentation, and I quote "If these fields are not used, they are set to 0. File readers only need to check to see if samplesPerPacket is 0."
  181.                 // in the case when a sound track is exported as a .mov using for example µLaw 2:1 compression these fields are filed with 'spore' aka 'grunge' so...
  182.                 // use GetCompressionInfo and check samplesPerPacket NOTE: Although better than nothing this will not always work, for example a .mov file with a SoundTrack
  183.                 // encoded using MACE where samples per packet will be greater than 1 but the information in the SoundDescriptionV1 record will be wrong
  184.                 CompressionInfo    compInfo;
  185.                 err = GetCompressionInfo(fixedCompression, (*sourceSoundDescription)->desc.dataFormat, (*sourceSoundDescription)->desc.numChannels, (*sourceSoundDescription)->desc.sampleSize, &compInfo);
  186.                 
  187.                 // numFrames = samples / samplesPerPacket
  188.                 // duration = numFrames * bytesPerFrame
  189.                 if (compInfo.samplesPerPacket == 1) {
  190.                     *outLength = (((numMediaSamples / compInfo.samplesPerPacket) * compInfo.bytesPerFrame));
  191.                 } else {
  192.                     *outLength = (((numMediaSamples / (*sourceSoundDescription)->samplesPerPacket) * (*sourceSoundDescription)->bytesPerFrame));
  193.                 }
  194.             }
  195.  
  196.             DisposeHandle(extension);
  197.             DisposeHandle((Handle)sourceSoundDescription);
  198.         }
  199.     }
  200.     
  201. bail:
  202.     return err;
  203. }
  204.  
  205. // * ----------------------------
  206. // PlaySound
  207. //
  208. // this function does the actual work of playing the sound file, it sets up the sound converter environment, allocates play buffers,
  209. // creates the sound channel and sends the appropriate sound commands to play the converted sound data
  210. OSErr PlaySound(const FSSpec *inFileToPlay)
  211. {
  212.     Handle                    hSoundData = NULL;
  213.     UInt32                    theLength = 0;
  214.     
  215.     AudioCompressionAtomPtr    theDecompressionAtom;
  216.     SoundComponentData        theInputFormat,
  217.                             theOutputFormat;
  218.     SoundConverter            mySoundConverter = NULL;
  219.     CmpSoundHeader            mySndHeader0,
  220.                             mySndHeader1;
  221.  
  222.     Ptr                        pSourceBuffer = NULL;
  223.     Ptr                        pDecomBuffer0 = NULL,
  224.                             pDecomBuffer1 = NULL;
  225.                         
  226.     Boolean                    isSoundDone = false;
  227.     FInfo                     fndrInfo;
  228.     
  229.     OSErr                     err = noErr;
  230.  
  231.     err = FSpGetFInfo(inFileToPlay, &fndrInfo);
  232.     BailErr(err);
  233.     
  234.     hSoundData = NewHandle(0);
  235.     if (hSoundData == NULL || MemError()) goto bail;
  236.     
  237.     // get what we need to do what we need to do
  238.     err = MyGetSoundDescriptionExtension(inFileToPlay, (AudioFormatAtomPtr *)&theDecompressionAtom, &mySndHeader0, hSoundData, &theLength);
  239.     if (noErr == err) {
  240.         HLock(hSoundData);
  241.         pSourceBuffer = *hSoundData;    // source buffer
  242.  
  243.         // setup input/output format for sound converter
  244.         theInputFormat.flags = 0;
  245.         theInputFormat.format = mySndHeader0.format;
  246.         theInputFormat.numChannels = mySndHeader0.numChannels;
  247.         theInputFormat.sampleSize = mySndHeader0.sampleSize;
  248.         theInputFormat.sampleRate = mySndHeader0. sampleRate;
  249.         theInputFormat.sampleCount = 0;
  250.         theInputFormat.buffer = NULL;
  251.         theInputFormat.reserved = 0;
  252.  
  253.         theOutputFormat.flags = 0;
  254.         theOutputFormat.format = kSoundNotCompressed;
  255.         theOutputFormat.numChannels = theInputFormat.numChannels;
  256.         theOutputFormat.sampleSize = theInputFormat.sampleSize;
  257.         theOutputFormat.sampleRate = theInputFormat.sampleRate;
  258.         theOutputFormat.sampleCount = 0;
  259.         theOutputFormat.buffer = NULL;
  260.         theOutputFormat.reserved = 0;
  261.  
  262.         err = SoundConverterOpen(&theInputFormat, &theOutputFormat, &mySoundConverter);
  263.         BailErr(err);
  264.  
  265.         // set up the sound converters decompresson 'environment' by passing in the 'magic' decompression atom
  266.         err = SoundConverterSetInfo(mySoundConverter, siDecompressionParams, theDecompressionAtom);
  267.         if (siUnknownInfoType == err) {
  268.             // clear this error, the decompressor didn't
  269.             // need the decompression atom and that's OK
  270.             err = noErr;
  271.         } else BailErr(err);
  272.         
  273.         UInt32    targetBytes = 32768,
  274.                 inputFrames = 0,
  275.                 inputBytes = 0,
  276.                 outputFrames = 0,
  277.                 outputBytes = 0,
  278.                 actualOutputBytes = 0,
  279.                 bytesConverted = 0,
  280.                 bytesPerFrame = 0;
  281.             
  282.         // find out how much buffer space to alocate for our output buffers
  283.         do {
  284.             targetBytes *= 2;
  285.             err = SoundConverterGetBufferSizes(mySoundConverter, targetBytes, &inputFrames, &inputBytes, &outputBytes);
  286.         } while (notEnoughBufferSpace == err  && targetBytes < (MaxBlock() / 4));
  287.         
  288.         bytesPerFrame = inputBytes / inputFrames;
  289.  
  290.         pDecomBuffer0 = NewPtr(outputBytes + kOverflowRoom);
  291.         BailErr(MemError(););
  292.         
  293.         pDecomBuffer1 = NewPtr(outputBytes + kOverflowRoom);
  294.         BailErr(MemError(););
  295.  
  296.         // convert two buffers of sound before we begin to do anything else
  297.         err = SoundConverterBeginConversion(mySoundConverter);
  298.         BailErr(err);
  299.  
  300.         // setup first header
  301.         mySndHeader0.samplePtr = pDecomBuffer0;
  302.         mySndHeader0.numChannels = theOutputFormat.numChannels;
  303.         mySndHeader0.sampleRate = theOutputFormat.sampleRate;
  304.         mySndHeader0.loopStart = 0;
  305.         mySndHeader0.loopEnd = 0;
  306.         mySndHeader0.encode = cmpSH;                    // compressed sound header encode value
  307.         mySndHeader0.baseFrequency = kMiddleC;
  308.         // mySndHeader0.AIFFSampleRate;                    // this is not used
  309.         mySndHeader0.markerChunk = NULL;
  310.         mySndHeader0.format = theOutputFormat.format;
  311.         mySndHeader0.futureUse2 = 0;
  312.         mySndHeader0.stateVars = NULL;
  313.         mySndHeader0.leftOverSamples = NULL;
  314.         mySndHeader0.compressionID = fixedCompression;    // compression ID for fixed-sized compression, even uncompressed sounds use fixedCompression
  315.         mySndHeader0.packetSize = 0;                    // the Sound Manager will figure this out for us
  316.         mySndHeader0.snthID = 0;
  317.         mySndHeader0.sampleSize = theOutputFormat.sampleSize;
  318.         mySndHeader0.sampleArea[0] = 0;                    // no samples here because we use samplePtr to point to our buffer instead
  319.  
  320.         // setup second header, only the buffer ptr is different
  321.         BlockMoveData(&mySndHeader0, &mySndHeader1, sizeof(mySndHeader0));
  322.         mySndHeader1.samplePtr = pDecomBuffer1;
  323.  
  324.         bytesConverted = 0;
  325.         err = SoundConverterConvertBuffer(mySoundConverter, pSourceBuffer, inputFrames, pDecomBuffer0, &outputFrames, &actualOutputBytes);
  326.         BailErr(err);
  327.         if (actualOutputBytes > outputBytes) DebugStr("\pYikes! Overflowed the outputbuffer");
  328.         bytesConverted += inputBytes;
  329.         mySndHeader0.numFrames = outputFrames;
  330.  
  331.         if (bytesConverted > theLength) {
  332.             isSoundDone = true;
  333.             inputBytes = 0;
  334.             inputFrames = 0;
  335.         } else if (bytesConverted + inputBytes > theLength) {
  336.             isSoundDone = true;
  337.             inputBytes = theLength - bytesConverted;
  338.             inputFrames = inputBytes / bytesPerFrame;
  339.         }
  340.         
  341.         err = SoundConverterConvertBuffer(mySoundConverter, pSourceBuffer + bytesConverted, inputFrames, pDecomBuffer1, &outputFrames, &actualOutputBytes);
  342.         BailErr(err);
  343.         if (actualOutputBytes > outputBytes) DebugStr("\pYikes! Overflowed the outputbuffer");
  344.         bytesConverted += inputBytes;
  345.         mySndHeader1.numFrames = outputFrames;
  346.  
  347.         // setup the callback, create the sound channel and play the sound
  348.         // we will continue to convert the sound data into the free (non playing) buffer
  349.         SndCallBackUPP theSoundCallBackUPP = NewSndCallBackUPP(MySoundCallBackFunction);
  350.         SndChannelPtr pSoundChannel = NULL;
  351.         
  352.         err = SndNewChannel(&pSoundChannel, sampledSynth, 0, theSoundCallBackUPP);
  353.  
  354.         if (err == noErr) {
  355.             
  356.             SndCommand        thePlayCmd0,
  357.                             thePlayCmd1,
  358.                             theCallBackCmd;
  359.             SndCommand         *pPlayCmd;
  360.             eBufferNumber   whichBuffer = kFirstBuffer;
  361.             
  362.             thePlayCmd0.cmd = bufferCmd;
  363.             thePlayCmd0.param1 = 0;                        // not used, but clear it out anyway just to be safe
  364.             thePlayCmd0.param2 = (long)&mySndHeader0;
  365.  
  366.             thePlayCmd1.cmd = bufferCmd;
  367.             thePlayCmd1.param1 = 0;                        // not used, but clear it out anyway just to be safe
  368.             thePlayCmd1.param2 = (long)&mySndHeader1;
  369.                         
  370.             whichBuffer = kFirstBuffer;                // buffer 1 will be free when callback runs
  371.             theCallBackCmd.cmd = callBackCmd;
  372.             theCallBackCmd.param2 = SetCurrentA5();
  373.  
  374.             gBufferDone = false;
  375.             err = SndDoCommand(pSoundChannel, &thePlayCmd0, true);
  376.  
  377.             if (noErr == err) {
  378.                 err = SndDoCommand(pSoundChannel, &theCallBackCmd, true);
  379.             }
  380.  
  381.             if (noErr == err) {
  382.                 err = SndDoCommand(pSoundChannel, &thePlayCmd1, true);
  383.             }
  384.             
  385.             Ptr pDecomBuffer = NULL;
  386.             CmpSoundHeaderPtr pSndHeader = NULL;
  387.             
  388.             if (noErr == err) {
  389.                 while (!isSoundDone && !Button()) {                        
  390.                     if (gBufferDone == true) {
  391.                         if (kFirstBuffer == whichBuffer) {
  392.                             pPlayCmd = &thePlayCmd0;
  393.                             pDecomBuffer = pDecomBuffer0;
  394.                             pSndHeader = &mySndHeader0;
  395.                             whichBuffer = kSecondBuffer;
  396.                         } else {
  397.                             pPlayCmd = &thePlayCmd1;
  398.                             pDecomBuffer = pDecomBuffer1;
  399.                             pSndHeader = &mySndHeader1;
  400.                             whichBuffer = kFirstBuffer;
  401.                         }
  402.  
  403.                         if (bytesConverted < theLength) {
  404.                             if (bytesConverted + inputBytes >= theLength) {
  405.                                 isSoundDone = true;
  406.                                 inputBytes = theLength - bytesConverted;
  407.                                 inputFrames = inputBytes / bytesPerFrame;
  408.                             }
  409.  
  410.                             err = SoundConverterConvertBuffer(mySoundConverter, pSourceBuffer + bytesConverted, inputFrames, pDecomBuffer, &outputFrames, &actualOutputBytes);
  411.                             if (err) break;
  412.                             if (actualOutputBytes > outputBytes) DebugStr("\pYikes! Overflowed the outputbuffer");
  413.                             bytesConverted += inputBytes;
  414.                             pSndHeader->numFrames = outputFrames;
  415.  
  416.                             gBufferDone = false;
  417.                             if (!isSoundDone) {
  418.                                 SndDoCommand(pSoundChannel, &theCallBackCmd, true);    // reuse callBackCmd
  419.                             }
  420.                             
  421.                             SndDoCommand(pSoundChannel, pPlayCmd, true);            // play the next buffer
  422.                         }
  423.                     }
  424.                 } // while
  425.             }
  426.             
  427.             SoundConverterEndConversion(mySoundConverter, pDecomBuffer, &outputFrames, &outputBytes);
  428.  
  429.             if (noErr == err && outputFrames) {
  430.                 pSndHeader->numFrames = outputFrames;
  431.                 SndDoCommand(pSoundChannel, pPlayCmd, true);    // play the last buffer.
  432.             }
  433.         }
  434.         
  435.         if (theSoundCallBackUPP)
  436.             DisposeSndCallBackUPP(theSoundCallBackUPP);
  437.  
  438.         if (pSoundChannel) {
  439.             err = SndDisposeChannel(pSoundChannel, false);        // wait until sounds stops playing before disposing of channel
  440.         }
  441.     }
  442.             
  443. bail:
  444.     if (mySoundConverter)
  445.         SoundConverterClose(mySoundConverter);
  446.         
  447.     if (pDecomBuffer0)
  448.         DisposePtr(pDecomBuffer0);
  449.         
  450.     if (pDecomBuffer1)
  451.         DisposePtr(pDecomBuffer1);
  452.         
  453.     if (hSoundData)
  454.         DisposeHandle(hSoundData);
  455.  
  456.     return err;
  457. }